//服务器
#pragma once
#include<iostream>
//////////// 做网络套接字基本需要的四个头文件 /////////////
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
//////////// 做网络套接字基本需要的四个头文件 /////////////
#include<string>
#include<functional>
#include<strings.h>
#include"Log.hpp"    //带进来日志
#include"InetAddr.hpp"   

const int defaultfd = -1;
using namespace LogModule;

//对于这里的 UdpServer    回调方法(要有文件描述符  sockfd)
using func_t = std::function<void(int sockfd,const std::string&,InetAddr&)>;

//带上回调函数  程序分层，UdpServer只负责通信，处理信息是回调到上层进行处理
//UdpServer 是为了进行网络通信
class UdpServer
{
public:
    //func表示希望服务器做什么样的任务处理
    UdpServer(uint16_t port,func_t func)   /* const std::string &ip, 不需要ip地址 */ 
        :_sockfd(defaultfd),
        _port(port),    //网络服务器 需要在外部把服务器所绑定的ip和端口 传给
        _isrunning(false),     //默认false  启动的时候是true
        _func(func)
    {}

    void Init()
    {
    //1.创建套接字（只是打开了网络文件,并没有赋予ip和端口）
        _sockfd = socket(AF_INET,SOCK_DGRAM,0);      //创建套接字  SOCK_DGRAM 表示用户数据报套接字
        if(_sockfd < 0)     //表示创建套接字失败
        {
            LOG(LogLevel::FATAL)<<"socket error!";       //创建套接字失败
            exit(1);
        }
        LOG(LogLevel::INFO)<<"socket success,sockfd : "<<_sockfd;

    //2.绑定socket信息，IP和端口，ip（比较特殊）
    //在绑定的时候，需要填充 sockaddr_in 结构体   第二个参数就是结构体
        struct sockaddr_in local;        //创建结构体（定义变量和对象，是在栈上开辟，用户栈中开辟属于用户）

        bzero(&local,sizeof(local));     //初始化他，全部清零

        local.sin_family = AF_INET;      //对应的协议家族，表示网络套接   填充协议家族（填充结构体）
   
    //IP地址信息和端口信息，一定要发送到网络！
    //本地格式 ---> 网络序列（需要转换  为大端）
        local.sin_port = htons(_port);   //填充端口号  把主机序列转为网络序列（大端传输） 

    //ip地址也是如此   1.IP转成4字节   2.4字节转成网络序列 ---> in_addr_t inet_addr(const char *cp)
    //转换接口有提供  字符串风格的ip转换成为正常的
    //不建议：自行手动绑定ip
        // local.sin_addr.s_addr = inet_addr(_ip.c_str());     


    //跨网络通信（任意ip地址均可访问）
        local.sin_addr.s_addr = INADDR_ANY;    //实际 INADDR_ANY 是宏 值为0   后续的server端ip可以不进行手动绑定
//////////////////  至此，就把结构体填充完毕（只是在栈上修改变量名，没有设置给内核）  //////////////////



    //直接就自动转换成为了网络序列
        // InetAddr addr("0",_port);
        // addr.NetAddr();    //这就拿到了网络地址

                

    //进行绑定
        int n = bind(_sockfd,(struct sockaddr*)&local,sizeof(local));       
        if(n < 0)
        {
            perror("bind errpr");
            LOG(LogLevel::FATAL) << "bind error";    //绑定失败直接退
            exit(2);
        }
        LOG(LogLevel::INFO) << "bind success,sockfd: " << _sockfd; 
    }

//服务器启动接口
    void Start()
    {
        //开启就不退了(死循环)
        _isrunning = true;
        while(_isrunning)
        {
            char buffer[1024];
            struct sockaddr_in peer;   //表示远端
            socklen_t len = sizeof(peer);   //封装的无符号整数    远端的长度
           
            //1.收消息
            ssize_t s = recvfrom(_sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);
            //尾两个参数：远端是谁，远端的长度
            if(s > 0)    //读到了有效的数据
            {
            //当有了InetAddr之后就不需要再进行下面这两行
                // int peer_port = ntohs(peer.sin_port);   //从网络中拿到网络序列 把网络序列转成主机序列 获取端口号
                // std::string peer_ip = inet_ntoa(peer.sin_addr);  //4字节网络风格的ip ---> 点分十进制的字符串风格的ip


            //客户端在访问服务器peer是client对应的网络地址（传进来）
                InetAddr client(peer);    
                buffer[s] = 0;   //当成字符串来看   最后的字符设为 \0    
            //服务器聚焦在只读取对应的数据
            //buffer是收到的消息，把收进来客户端的buffer消息，传递给回调函数，回调函数处理
            //收到的数据直接进行回调处理，把数据交给上层UdpServer.cc 处理
            _func(_sockfd,buffer,client);     
            //回调函数，调出去再回来   拿到结果，把结果返回去(不用接收返回值，直接回调)

            //2.发消息
                // sendto(_sockfd,result.c_str(),result.size(),0,(struct sockaddr*)&peer,len);
            }
        //该服务器 只做读发消息，不做对应的处理，只做通信
        }
    }

    ~UdpServer()
    {}

private:
    int _sockfd;
    uint16_t _port;
    // std::string _ip;    
    //当前服务器未来所对应的IP   用的是字符串风格，点分十进制   192.168.1.1   不需要手动绑定，不需要给，只需要给端口号即可

    bool _isrunning;    //这是判断服务器是不是退出


    func_t _func;    //服务器的回调函数，用来对数据进行处理！
};
